package ar.com.pragmatia.lanacion.data;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Vector;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.content.ContentValues;
import android.database.Cursor;
import ar.com.pragmatia.lanacion.app.App;
import ar.com.pragmatia.lanacion.utils.Strings;

public class AppData implements Datable {

	private String id;
	private String action;

	public void load(JSONObject object) {

		if (object != null) {
			try {

				if (object.has("act")) {
					setAction((String) object.get("act"));
				}

				Class<?> clazz = getClazz();

				String[] fields = getFieldsName();
				for (int i = 0; i < fields.length; i++) {
					try {
						if (object.has(fields[i])) {
							executeStringSetterMethod(clazz,
									"set" + Strings.camelize(fields[i], false),
									object.getString(fields[i]));
						}
					} catch (SecurityException e) {
						App.log("AppData", "save", e.getMessage());
					} catch (IllegalArgumentException e) {
						App.log("AppData", "save", e.getMessage());
					} catch (NoSuchMethodException e) {
						App.log("AppData", "save", e.getMessage());
					}
				}

			} catch (JSONException e) {
				App.log("AppData", "load", e.getMessage());
			}
		}
	}

	public void load(Cursor c, int position) {

		String[] fields = getFieldsName();

		Class<?> clazz = getClazz();

		c.moveToPosition(position);

		for (int i = 0; i < fields.length; i++) {
			try {
				executeStringSetterMethod(clazz,
						"set" + Strings.camelize(fields[i], false),
						c.getString(i));
			} catch (SecurityException e) {
				App.log("AppData", "save", e.getMessage());
			} catch (IllegalArgumentException e) {
				App.log("AppData", "save", e.getMessage());
			} catch (NoSuchMethodException e) {
				App.log("AppData", "save", e.getMessage());
			}
		}

	}

	// JSONArray array = new JSONArray();
	// array.put(value)

	public JSONObject getJSONObjec() {

		JSONObject object = new JSONObject();

		Class<?> clazz = getClazz();

		String[] fields = getFieldsName();
		for (int i = 0; i < fields.length; i++) {
			try {
				object.put(fields[i], Strings
						.addToJson(executeStringGetterMethod(clazz, "get"
								+ Strings.camelize(fields[i], false))));
			} catch (NoSuchMethodException e) {
				App.log("AppData", "getJson", e.getMessage());
			} catch (JSONException e) {
				App.log("AppData", "getJson", e.getMessage());
			}
		}

		return object;

	}

	public AppData findById(String id) {
		if (Strings.isNullOrEmpty(id)) {
			return null;
		} else {
			return findById(Integer.parseInt(id));
		}
	}

	public AppData findById(int id) {
		Vector<AppData> r = find("where id = " + id);
		if (r.size() == 1) {
			return r.get(0);
		} else {
			return null;
		}
	}

	public Vector<AppData> find() {
		return find("");
	}

	public Vector<AppData> find(String where) {

		Vector<AppData> records = new Vector<AppData>();

		String[] fields = getFieldsName();

		String sql = "SELECT " + Strings.join(fields, ",") + " FROM "
				+ getTableName();
		if (!Strings.isNullOrEmpty(where)) {
			sql += " " + where;
		}

		Cursor c = App.getDataHelper().getDb().rawQuery(sql, null);
		Class<?> clazz = getClazz();

		if (c.getCount() > 0) {

			c.moveToFirst();
			while (c.isAfterLast() == false) {

				AppData data = DataFactory.getClass(getTableName());

				for (int i = 0; i < fields.length; i++) {
					try {
						executeStringSetterMethod(data, clazz,
								"set" + Strings.camelize(fields[i], false),
								c.getString(i));
					} catch (SecurityException e) {
						App.log("AppData", "save", e.getMessage());
					} catch (IllegalArgumentException e) {
						App.log("AppData", "save", e.getMessage());
					} catch (NoSuchMethodException e) {
						App.log("AppData", "save", e.getMessage());
					}
				}

				records.add((AppData) data);

				c.moveToNext();
			}
		}
		c.close();
		// Cursor c = query(sql);
		// records = getObjectFromCursor(c);
		// String className = Strings
		// .camelize(Strings.singularize(getTableName()));
		// App.log("AppData", "find", className);

		return records;
	}

	public Vector<AppData> query(String sql) {
		Vector<AppData> records = new Vector<AppData>();

		String[] fields = getFieldsName();


		Cursor c = App.getDataHelper().getDb().rawQuery(sql, null);
		Class<?> clazz = getClazz();

		if (c.getCount() > 0) {

			c.moveToFirst();
			while (c.isAfterLast() == false) {

				AppData data = DataFactory.getClass(getTableName());

				for (int i = 0; i < fields.length; i++) {
					try {
						executeStringSetterMethod(data, clazz,
								"set" + Strings.camelize(fields[i], false),
								c.getString(i));
					} catch (SecurityException e) {
						App.log("AppData", "save", e.getMessage());
					} catch (IllegalArgumentException e) {
						App.log("AppData", "save", e.getMessage());
					} catch (NoSuchMethodException e) {
						App.log("AppData", "save", e.getMessage());
					}
				}

				records.add((AppData) data);

				c.moveToNext();
			}
		}
		c.close();
		// Cursor c = query(sql);
		// records = getObjectFromCursor(c);
		// String className = Strings
		// .camelize(Strings.singularize(getTableName()));
		// App.log("AppData", "find", className);

		return records;
	}

	// public Vector<AppData> getObjectFromCursor(Cursor c) {
	// Vector<AppData> records = new Vector<AppData>();
	// String[] fields = getFieldsName();
	//
	// Class<?> clazz = getClazz();
	//
	// if (c.getCount() > 0) {
	//
	// c.moveToFirst();
	// while (c.isAfterLast() == false) {
	//
	// AppData data = DataFactory.getClass(getTableName());
	//
	// for (int i = 0; i < fields.length; i++) {
	// try {
	// executeStringSetterMethod(data, clazz,
	// "set" + Strings.camelize(fields[i], false),
	// c.getString(i));
	// } catch (SecurityException e) {
	// App.log("AppData", "save", e.getMessage());
	// } catch (IllegalArgumentException e) {
	// App.log("AppData", "save", e.getMessage());
	// } catch (NoSuchMethodException e) {
	// App.log("AppData", "save", e.getMessage());
	// }
	// }
	//
	// records.add((AppData) data);
	//
	// c.moveToNext();
	// }
	// }
	// c.close();
	// return records;
	// }

	public Cursor findAll(String where) {

		String[] fields = getFieldsName();

		String sql = "SELECT " + Strings.join(fields, ",") + " FROM "
				+ getTableName();
		if (!Strings.isNullOrEmpty(where)) {
			sql += " " + where;
		}

		return App.getDataHelper().getDb().rawQuery(sql, null);
	}

	/**
	 * Saves an object to it's corresponding table. The method itself decide to
	 * update an exiting record or insert a new one.
	 * 
	 */
	public boolean save() {

		String fields[] = getFieldsName();

		Class<?> clazz = getClazz();

		// SQLiteStatement insertStmt = null;
		ContentValues values = new ContentValues();

		// determine if should do an insert or an update
		String action = "update";
		if (!Strings.isNullOrEmpty(getAction())) {
			if (getAction().equals("I")) {
				action = "insert";
			} else if (getAction().equals("U")) {
				action = "update";
			} else if (getAction().equals("D")) {
				action = "delete";
			} else {
				App.log("AppData", "save", "Receive bad action from BE");
			}
		} else {
			try {
				if (Strings.isNullOrEmpty(executeStringGetterMethod(clazz,
						"getId"))) {
					action = "insert";
				}
			} catch (NoSuchMethodException e) {
				// e.printStackTrace();
				App.log("AppData", "save", e.getMessage());
			}
		}

		for (int i = 0; i < fields.length; i++) {

			String value = null;
			try {
				value = executeStringGetterMethod(clazz,
						"get" + Strings.camelize(fields[i], false));
			} catch (NoSuchMethodException e) {
				// e.printStackTrace();
				// App.log("AppData", "save", this.getClass().getName());
				App.log("AppData", "save", e.getMessage());
			}

			if (fields[i].equals("id") && Strings.isNullOrEmpty(value)) {
				continue;
			} else {
				value = (Strings.isNullOrEmpty(value)) ? "" : value;
				values.put(fields[i], value);
			}

		}

		long r = -1;

		try {
			if (action.equals("insert")) {
				r = App.getDb().insert(getTableName(), null, values);
			} else if (action.equals("update")) {
				r = App.getDb().update(getTableName(), values, "id=?",
						new String[] { getId() });
			} else if (action.equals("delete")) {
				r = App.getDb().delete(getTableName(), "id = " + getId(), null);
			}
		} catch (Exception e) {
			e.printStackTrace();
			App.log("AppData", "save", e.getMessage());
		}

		return (r >= 0) ? true : false;

	}

	/**
	 * Creates a Class object for the current class.
	 * 
	 * @return
	 */
	private Class<?> getClazz() {

		String className = this.getClass().getPackage().toString().substring(8)
				+ "." + this.getClass().getSimpleName();

		Class<?> clazz = null;
		try {
			clazz = Class.forName(className);
		} catch (ClassNotFoundException e) {
			App.log("AppData", "save", e.getMessage());
		}

		return clazz;
	}

	/**
	 * Search for a method in a given class or in it's superclass till it get's
	 * it. If found, the method will be invoked and a string value will be
	 * setted.
	 * 
	 * @param clazz
	 * @param methodName
	 * @param value
	 * @throws NoSuchMethodException
	 */
	protected void executeStringSetterMethod(Class<?> clazz, String methodName,
			String value) throws NoSuchMethodException {
		executeStringSetterMethod(this, clazz, methodName, value);
	}

	protected void executeStringSetterMethod(AppData data, Class<?> clazz,
			String methodName, String value) throws NoSuchMethodException {

		try {
			Method m = clazz.getDeclaredMethod(methodName, String.class);
			try {
				Object arglist[] = { value };
				m.invoke(data, arglist);
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				e.printStackTrace();
			}
		} catch (NoSuchMethodException e) {

			if (clazz != Object.class) {
				executeStringSetterMethod(data, clazz.getSuperclass(),
						methodName, value);
			} else {
				throw e;
			}

		}

	}

	/**
	 * Search for a method in a given class or in it's superclass till it get's
	 * it. If found, the method will be invoked and a string value will be
	 * returned to the caller.
	 * 
	 * @param clazz
	 * @param methodName
	 * @return
	 * @throws NoSuchMethodException
	 */
	protected String executeStringGetterMethod(Class<?> clazz, String methodName)
			throws NoSuchMethodException {

		String r = "";

		try {
			Method m = clazz.getDeclaredMethod(methodName);
			try {
				r = (String) m.invoke(this);
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				e.printStackTrace();
			}
		} catch (NoSuchMethodException e) {

			if (clazz == Object.class) {
				throw e;
			}

			return executeStringGetterMethod(clazz.getSuperclass(), methodName);
		}

		return r;
	}

	public boolean loadAndSave(JSONArray objectsArray) {

		boolean errors = false;

		try {
			for (int i = 0; i < objectsArray.length(); i++) {
				load(objectsArray.getJSONObject(i));
				if (!save()) {
					errors = true;
				}
			}
		} catch (JSONException e) {
			errors = true;
			App.log("AppData", "loadAndSave", e.getMessage());
		}

		return !errors;
	}

	/******************************************************************************
	 * SETTERS & GETTERS
	 *****************************************************************************/
	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getAction() {
		return action;
	}

	public void setAction(String action) {
		this.action = action;
	}

	/**
	 * Gets the fields name
	 * 
	 */
	public String[] getFieldsName() {
		Field field;
		try {
			field = getClazz().getDeclaredField("FIELDS");
			field.setAccessible(true);
			return (String[]) field.get(getClazz());
		} catch (SecurityException e) {
			App.log("AppData", "getFieldsName", e.getMessage());
		} catch (NoSuchFieldException e) {
			App.log("AppData", "getFieldsName", e.getMessage());
		} catch (IllegalArgumentException e) {
			App.log("AppData", "getFieldsName", e.getMessage());
		} catch (IllegalAccessException e) {
			App.log("AppData", "getFieldsName", e.getMessage());
		}

		return null;
	}

	/**
	 * Gets the table name
	 * 
	 */
	public String getTableName() {
		Field field;
		try {
			field = getClazz().getDeclaredField("TABLE_NAME");
			return (String) field.get(this);
		} catch (SecurityException e) {
			App.log("AppData", "getTableName", e.getMessage());
		} catch (NoSuchFieldException e) {
			App.log("AppData", "getTableName", e.getMessage());
		} catch (IllegalArgumentException e) {
			App.log("AppData", "getTableName", e.getMessage());
		} catch (IllegalAccessException e) {
			App.log("AppData", "getTableName", e.getMessage());
		}

		return null;
	}

}
